package com.atguigu.spring.aop.annotation;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

import java.util.Arrays;

/**
 * 1.在切面中，需要通过指定的注解将方法表示为通知方法
 * @Before:前置通知，在目标对象方法执行之前执行
 * @After：后置通知，在目标对象方法的finally子句中执行
 * @AfterReturning:返回通知，在目标对象方法返回值之后执行
 * @AfterThrowing:异常通知，在目标对象方法的catch子句中执行
 *
 * 2.切入点表达式：设置在标识通知的注解的value属性中
 *  @Before("execution(public int com.atguigu.spring.aop.annotation.CalculatorImpl.add(int, int))")
 *  @Before("execution(* com.atguigu.spring.aop.annotation.CalculatorImpl.*(..))")
 *  第一个*表示任意的访问修饰符和返回值的类型
 *  第二个*表示类中任意的方法
 *  ..表示任意的参数列表
 *  类的地方也可以使用*，表示包下所有的类
 *
 * 3.重用切入点表达式
 *  //@Pointcut声明一个公共的切入点表达式
 *  @Pointcut("execution(* com.atguigu.spring.aop.annotation.CalculatorImpl.*(..))")
 *  public void pointCut(){}
 *  使用方式：@Before("pointCut()")
 *
 * 4.获取连接点信息
 *  在通知方法的参数位置，设置JoinPoint类型的参数，就可以获取连接点所对应方法的信息
 *  //获取连接点所对应的方法的签名信息
 *  Signature signature = joinPoint.getSignature();
 *  //获取连接点所对应方法的参数
 *  Object[] args = joinPoint.getArgs();
 *
 * 5.切面的优先级
 * 可以通过@Order注解的value属性设置优先级，默认值Integer的最大值
 * @Order 注解的value属性值越小，优先级越高
 */
@Component
@Aspect//将类标识为一个切面
public class LoggerAspect {

    @Pointcut("execution(* com.atguigu.spring.aop.annotation.CalculatorImpl.*(..))")
    public void pointCut(){}

    //@Before("execution(public int com.atguigu.spring.aop.annotation.CalculatorImpl.add(int, int))")
    //@Before("execution(* com.atguigu.spring.aop.annotation.CalculatorImpl.*(..))")
    @Before("pointCut()")
    public void beforeAdviceMethod(JoinPoint joinPoint){
        //获取连接点所对应的方法的签名信息
        Signature signature = joinPoint.getSignature();
        //获取连接点所对应方法的参数
        Object[] args = joinPoint.getArgs();
        System.out.println("LoggerAspect, 方法：" + signature.getName() + "参数：" + Arrays.toString(args));
    }

    @After("pointCut()")
    public void afterAdviceMethod(){
        System.out.println("LoggerAspect,后置通知");
    }

    /**
     * 在返回通知中若要获取目标对象方法的返回值
     * 只需要通过@AfterReturning注解的returning属性
     * 就可以将通知方法的某个参数指定为接受目标对象方法的返回值的参数
     * @param joinPoint
     * @param result
     */
    @AfterReturning(value = "pointCut()", returning = "result")
    public void afterReturningAdviceMethod(JoinPoint joinPoint, Object result) {
        //获取连接点所对应方法的签名信息
        String methodName = joinPoint.getSignature().getName();
        System.out.println("Logger-->返回通知，方法名："+methodName+"，结果："+result);
    }

    /**
     * 在返回通知中若要获取目标对象方法的异常
     * 只需要通过@AfterReturning注解的throwing属性
     * 就可以将通知方法的某个参数指定为接受目标对象方法出现异常的参数
     * @param joinPoint
     * @param ex
     */
    @AfterThrowing(value = "pointCut()", throwing = "ex")
    public void afterThrowingAdviceMethod(JoinPoint joinPoint, Throwable ex){
        //获取连接点所对应方法的签名信息
        String methodName = joinPoint.getSignature().getName();
        System.out.println("Logger-->异常通知，方法名："+methodName+"，异常：" + ex);
    }

    /**
     * 环绕通知返回值一定要与目标方法返回值类型一致
     * @param joinPoint
     * @return
     */
    @Around("pointCut()")
    public Object aroundMethod(ProceedingJoinPoint joinPoint){
        String methodName = joinPoint.getSignature().getName();
        String args = Arrays.toString(joinPoint.getArgs());
        Object result = null;
        try {
            System.out.println("环绕通知-->目标对象方法执行之前");
            //目标方法的执行，目标方法的返回值一定要返回给外界调用者
            result = joinPoint.proceed();
            System.out.println("环绕通知-->目标对象方法返回值之后");
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            System.out.println("环绕通知-->目标对象方法出现异常时");
        } finally {
            System.out.println("环绕通知-->目标对象方法执行完毕");
        }
        return result;
    }
}
